#ifndef __SLOTHTTPD_RECEIVE_BUFFER_HPP__
#define __SLOTHTTPD_RECEIVE_BUFFER_HPP__

#include <slothttpd/generic_pool.hpp>
#include <slothttpd/types.hpp>

#include <cstring>
#include <cstddef>

#include <ostream>

#include <boost/intrusive/list.hpp>

namespace slothttpd {

/**
 * @brief buffer with unprocessed data
 *
 *   [##############?????????????????????...............]
 *   ^              ^                   ^               ^
 * data     unprocessed_first    unprocessed_last      data+size
 */
struct data_buffer :
        public boost::intrusive::list_base_hook<boost::intrusive::link_mode<boost::intrusive::auto_unlink> >
{
    friend std::ostream &operator<<(std::ostream &os, const data_buffer &b);
public:
    data_buffer(char *p, std::size_t bytes);
    data_buffer(void *p, std::size_t bytes);
    data_buffer() noexcept;
    char_range_t unprocessed() noexcept;
    char_range_t processed() noexcept;
    bool full() const noexcept;
    char_range_t free_space() noexcept;
    void remove_processed_data() noexcept;
    void reset() noexcept;
    void provide(std::size_t n);
    void consume(std::size_t n = 1);
    
    static data_buffer from_pool(generic_pool &pool, std::size_t size);
    
    char           *unprocessed_first;
    char           *unprocessed_last;
    char           *data;
    std::size_t     size;
};

class receive_buffer_list 
{
    friend std::ostream &operator<<(std::ostream &os, const receive_buffer_list &list);
public:
    typedef boost::intrusive::list<data_buffer, boost::intrusive::constant_time_size<false> > list_t;
    
    receive_buffer_list(std::size_t s, std::size_t m, bool save_buffers = true);
    ~receive_buffer_list();
    
    char_range_t get_free_space();
    char_range_t get_data();
    void provide(std::size_t n);
    void consume(std::size_t n);
    data_buffer &current();
    data_buffer &prev();
    void reset();
    void switch_dont_save();
    bool took_new() const;
    void free();
    void disable();
    
private:
    void ready_buffer(data_buffer &b);
    void consume_buffer(data_buffer &b);
    void empty_buffer(data_buffer &b);
    void empty_buffer_list(list_t &list);
    std::size_t total_buffers() const;
    
    static data_buffer *allocate(std::size_t buffer_size);
    void free_list(list_t &l);
    
    list_t empty_;
    list_t ready_;
    list_t consumed_;
    
    bool save_buffers_;
    bool took_new_;
    
public:
    const std::size_t buffer_size;
    std::size_t max_buffers;
};

}

#endif
